﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{

    /// <summary>
    /// Euclidean color filtering.
    /// </summary>
    ///
    /// <remarks><para>The filter filters pixels, which color is inside/outside
    /// of RGB sphere with specified center and radius - it keeps pixels with
    /// colors inside/outside of the specified sphere and fills the rest with
    /// <see cref="FillColor">specified color</see>.</para>
    ///
    /// <para>The filter accepts 24 and 32 bpp color images for processing.</para>
    ///
    /// <para>Sample usage:</para>
    /// <code>
    /// // create filter
    /// EuclideanColorFiltering filter = new EuclideanColorFiltering( );
    /// // set center colol and radius
    /// filter.CenterColor = Color.FromArgb( 215, 30, 30 );
    /// filter.Radius = 100;
    /// // apply the filter
    /// filter.ApplyInPlace( image );
    /// </code>
    ///
    /// <para><b>Initial image:</b></para>
    /// <img src="img/imaging/sample1.jpg" width="480" height="361" />
    /// <para><b>Result image:</b></para>
    /// <img src="img/imaging/euclidean_filtering.jpg" width="480" height="361">
    /// </remarks>
    ///
    /// <seealso cref="ColorFiltering"/>
    ///
    public class EuclideanColorFiltering : BaseImageFilter
    {
        private short radius = 100;
        private Color m_CenterColor = Color.FromArgb(255, 255, 255);
        private Color m_FillColor = Color.FromArgb(0, 0, 0);
        private bool m_bFillOutside = true;

        // private format translation dictionary
        private Dictionary<PixelFormat, PixelFormat> formatTransalations = new
        Dictionary<PixelFormat, PixelFormat>();

        /// <summary>
        /// Format translations dictionary.
        /// </summary>
        //public override Dictionary<PixelFormat, PixelFormat> FormatTransalations
        //{
        //    get { return formatTransalations; }
        //}

        /// <summary>
        /// RGB sphere's radius, [0, 450].
        /// </summary>
        ///
        /// <remarks>Default value is 100.</remarks>
        ///
        public short Radius
        {
            get { return radius; }
            set { radius = System.Math.Max((short)0, System.Math.Min((short) 450, value)); }
        }

        /// <summary>
        /// RGB sphere's center.
        /// </summary>
        ///
        /// <remarks>Default value is (255, 255, 255) - white color.</remarks>
        ///
        public Color CenterColor
        {
            get { return m_CenterColor; }
            set { m_CenterColor = value; }
        }

        /// <summary>
        /// Fill color used to fill filtered pixels.
        /// </summary>
        public Color FillColor
        {
            get { return m_FillColor; }
            set { m_FillColor = value; }
        }

        /// <summary>
        /// Determines, if pixels should be filled inside or outside specified
        /// RGB sphere.
        /// </summary>
        public bool FillOutside
        {
            get { return m_bFillOutside; }
            set { m_bFillOutside = value; }
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="EuclideanColorFiltering"/> class.
        /// </summary>
        public EuclideanColorFiltering()
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_euclideanColorFiltering"></param>
        internal EuclideanColorFiltering(EuclideanColorFiltering _euclideanColorFiltering)
        {
            this.m_bFillOutside = _euclideanColorFiltering.m_bFillOutside;
            this.m_FillColor = _euclideanColorFiltering.m_FillColor;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EuclideanColorFiltering"/> class.
        /// </summary>
        ///
        /// <param name="center">RGB sphere's center.</param>
        /// <param name="radius">RGB sphere's radius.</param>
        ///
        public EuclideanColorFiltering(Color center, short radius) :
            this()
        {
            this.m_CenterColor = center;
            this.radius = radius;
        }

        /// <summary>
        /// Process the filter on the specified image.
        /// </summary>
        ///
        /// <param name="image">Source image data.</param>
        public override UnsafeBitmap Execute(UnsafeBitmap image)
        {
            unsafe
            {
                int pixelSize = 4; //image.BytesPerPixelCount;

                int startX = 0;
                int startY = 0;
                int stopX = startX + image.Width;
                int stopY = startY + image.Height;
                int offset = image.BitmapData.Stride - image.Width * pixelSize;

                //    byte r, g, b;
                // sphere's center
                byte cR = m_CenterColor.R;
                byte cG = m_CenterColor.G;
                byte cB = m_CenterColor.B;
                // fill color
                byte fR = m_FillColor.R;
                byte fG = m_FillColor.G;
                byte fB = m_FillColor.B;

                // do the job
                byte* ptr = (byte*)image.BitmapData.Scan0.ToPointer();

                // allign pointer to the first pixel to process
                ptr += (startY * image.BitmapData.Stride + startX * pixelSize);

                int r = 0;
                int g = 0;
                int b = 0;
                // for each row
                for (int y = startY; y < stopY; y++)
                {
                    // for each pixel
                    for (int x = startX; x < stopX; x++, ptr += pixelSize)
                    {
                        r = ptr[2];
                        g = ptr[1];
                        b = ptr[0];

                        // calculate the distance
                        if ((int)Math.Sqrt(
                            Math.Pow((int)r - (int)cR, 2) +
                            Math.Pow((int)g - (int)cG, 2) +
                            Math.Pow((int)b - (int)cB, 2)) <= radius)
                        {
                            // inside sphere
                            if (!m_bFillOutside)
                            {
                                ptr[2] = fR;
                                ptr[1] = fG;
                                ptr[0] = fB;
                            }
                        }
                        else
                        {
                            // outside sphere
                            if (m_bFillOutside)
                            {
                                ptr[2] = fR;
                                ptr[1] = fG;
                                ptr[2] = fB;
                            }
                        }
                    }
                    ptr += offset;
                    //    
                }
                return image;
            }
        }

        /// <summary>
        /// used to clone this instance
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new EuclideanColorFiltering(this);
        }
    }
}
